package incrpatch

import (
	"fmt"
	"reflect"
	"strings"

	"gitee.com/terender/sfw/rlog"
)

/*
	文档型数据结构的读取、修改和打包工具

	对传入数据实体有如下要求:
		1. 必须传入一个 Addressable 的变量作为数据实体。

		1. 叶子节点字段只能是 {基本类型}(数字、string、bool)，数字可以是浮点数和整数，
		   对应变量为 [u]intx -> x in [8,16,32,64], byte, float32, float64, string, bool

		2. 非叶子节点字段只能是 {复合类型}(k-v、list)，
		   对应变量类型为 struct、*struct、map、slice、array。

		3. map 类型字段，其 map-key 必须是 string 类型，其 map-value 可以是上述 {基本类型} 和 {复合类型}，
		   如果 map-value 需要 {复合类型}，建议使用 struct 和 *struct，不推荐用 map、slice、array

		4. 不能存在 *{基本类型}、*map、*slice、*array 类型的成员变量。


	数据实体生成绑定的 Packer 后，可以通过 {字段全名} (有时简称 key) 来读取和修改字段的值，字段名转换默认规则为
		1. 成员变量名转为小写
		2. 数组元素用其索引数字
		3. map-key 转为小写
	多个层级用 '.' 连接


	一旦对一个数据实体生成了绑定的 Packer，则有如下要求:
	    1. 对此数据实体内所有字段的改动操作必须通过 Packer 的 Set 方法来进行，不能直接修改数据实体的成员变量，否则会导 Packer 致无法记录数据改动。
		2. 对此数据实体内字段的读取操作，可以通过 Packer 的 Get 方法，也可以通过直接读取数据实体的成员变量的方法。
	    3. Packer 的 Get Set Pack 三个方法是非原子性非线程安全的，因此这三个方法以及对数据实体的成员变量的直接读取操作，
		   需要由调用者自行维护其原子性(加锁或者只在一个线程中操作)。
*/

// Serializer 序列化器方法定义，用于将提取出来的数据字段序列化，传入参数分别为
// up: 发生更新的字段数据
// del: 删除的字段
type Serializer func(up map[string]interface{}, del map[string]bool) (interface{}, error)

// Packer 脏数据打包器，与一个数据实体是绑定关系
type Packer struct {
	data                interface{}   // 传入的数据实体
	refTable            *ReflectTable // 传入数据实体的固定字段全反射表
	records             *IncrRecords  // 传入数据结构的增量更新记录
	packSequenceCounter int           // 打包序列号计数器
	serializer          Serializer    // 序列化器
}

// NewPacker Packer 构造函数，传入数据实体，生成与其绑定的打包器
func NewPacker(data interface{}, f Serializer) (*Packer, error) {
	root := reflect.ValueOf(data)
	if root.Kind() == reflect.Interface {
		root = root.Elem()
	}
	for root.Kind() == reflect.Ptr {
		root = root.Elem()
	}
	if !root.IsValid() {
		return nil, fmt.Errorf(`传入的数据实体是无效值`)
	}
	if !root.CanAddr() {
		return nil, fmt.Errorf(`不能对 Unaddressable 的值生成打包器，因为它不能通过反射来进行赋值操作`)
	}
	packer := &Packer{
		data:       data,
		refTable:   NewReflectTable(root),
		records:    NewIncrRecords(),
		serializer: f,
	}
	return packer, nil
}

// Records 返回打包器中的改动记录
func (p *Packer) Records() *IncrRecords { return p.records }

// Get 通过全反射结构表查询指定 key 对应的值
func (p *Packer) Get(key string) (interface{}, error) {

	key = strings.TrimPrefix(key, `.`)
	key = strings.TrimSuffix(key, `.`)
	key = strings.ToLower(key)

	// 如果在固定字段反射表中找到，表示此字段为固定字段
	v, ok := p.refTable.GetValue(key)
	if ok {
		return v.Interface(), nil
	}

	// 否则寻找其父节点的 key、在父节点中的 subkey，父节点的反射值
	// 在父节点上用反射方法通过 subkey 来获取字段的值
	parentValue, subkey := p.refTable.FindParentOf(key)
	v, ok = FindChildValueReflect(parentValue, subkey)
	if !ok {
		return nil, fmt.Errorf("key[%s] don't find in reflectStruct ", key)
	}
	return v.Interface(), nil
}

// Set 通过全反射结构表设置指定 key 的值
func (p *Packer) Set(key string, value interface{}) error {
	// 格式化 key
	key = strings.TrimPrefix(key, `.`)
	key = strings.TrimSuffix(key, `.`)
	key = strings.ToLower(key)

	// 如果在固定字段反射表中找到，表示此字段为固定字段
	v, ok := p.refTable.GetValue(key)
	if ok {
		err := SetValueReflect(v, value)
		if err != nil {
			return err
		}
	} else {
		// 否则寻找其父节点的 key、在父节点中的 subkey，父节点的反射值
		// 在父节点上用反射方法通过 subkey 来对字段赋值
		parentValue, subkey := p.refTable.FindParentOf(key)
		trimKey, err := SetChildValueReflect(parentValue, subkey, value)
		if err != nil {
			return err
		}
		if len(trimKey) > 0 {
			key = strings.TrimRight(key, strings.Join(trimKey, `.`))
			key = strings.TrimRight(key, `.`)
		}
	}
	if value != nil {
		p.records.Update(key)
	} else {
		p.records.Delete(key)
	}
	return nil
}

// Pack 根据脏标记列表打增量补丁包并放入等待队列
// 为避免产生数据竞争，打包同时就用外部传入的序列化方法立刻将数据序列化
func (p *Packer) Pack() (interface{}, error) {
	if p.serializer == nil {
		return nil, fmt.Errorf(`打包器中没有传入序列化方法`)
	}
	if !p.records.Dirty() {
		return nil, nil
	}

	updates := map[string]interface{}{}
	deletes := map[string]bool{}
	for i, ver := range p.records.records {
		if ver == 0 {
			rlog.Errorf(`字段[%v]在改动记录列表中存在, 但是版本号为0`, i)
			continue
		}
		if ver < 0 {
			deletes[i] = true
			continue
		}
		v, err := p.Get(i)
		if err != nil {
			rlog.Errorf(`打包增量更新包时 字段[%v] 在更新标记列表中存在，但是找不到对应数据`, i)
			//patch.Deletes[i] = nil
			continue
		}
		updates[i] = v
	}

	serialized, err := p.serializer(updates, deletes)
	if err != nil {
		return nil, err
	}
	p.packSequenceCounter++
	p.records.Reset()
	return serialized, nil
}
